#![cfg_attr(
    all(not(debug_assertions), target_os = "windows"),
    windows_subsystem = "windows"
)]

use std::{sync::{Arc, Mutex}, thread, fs::File};

use updown::{client::Connection, util};

struct Data {
    connection: Arc<Mutex<Option<Connection>>>
}

fn get_file_len(path: &str) -> u64 {
    if let Ok(file) = File::open(path) {
        if let Ok(meta) = file.metadata() {
            return meta.len();
        }
    }
    0
}


#[tauri::command]
fn connect(window: tauri::Window, state: tauri::State<Data>, host: String, username: String, password: String, register: bool) {
    let conn = state.connection.clone();
    thread::spawn(move || {
        if let Ok(mut conn) = conn.try_lock() {
            if let None = *conn {
                match Connection::connect(
                    &host,
                    "updown.op",
                    Some("../cert/updown.op.crt"),
                    &username,
                    &password,
                    register
                ) {
                    Ok(new_conn) => {
                        *conn = Some(new_conn);
                        window.emit("connect", None::<i32>).unwrap();
                    },
                    Err(e) => window.emit("error", e).unwrap()
                }
            } else {
                window.emit("error", "Already connected").unwrap();
            }
        } else {
            window.emit("error", "System busy").unwrap();
        }
    });
}

#[tauri::command]
fn disconnect(window: tauri::Window, state: tauri::State<Data>) {
    let conn = state.connection.clone();
    thread::spawn(move || {
        if let Ok(mut conn) = conn.try_lock() {
            if let Some(_) = *conn {
                *conn = None;
                window.emit("disconnect", None::<i32>).unwrap();
            } else {
                window.emit("error", "Not connected").unwrap();
            }
        } else {
            window.emit("error", "System busy").unwrap();
        }
    });
}

#[tauri::command]
fn fetch_list(window: tauri::Window, state: tauri::State<Data>, part: bool) {
    let conn = state.connection.clone();
    thread::spawn(move || {
        if let Ok(mut conn) = conn.try_lock() {
            if let Some(conn) = &mut *conn {
                match conn.fetch_list(part) {
                    Ok(list) => window.emit("list", (part, list)).unwrap(),
                    Err(e) => window.emit("error", e).unwrap()
                }
            } else {
                window.emit("error", "Not connected").unwrap();
            }
        } else {
            window.emit("error", "System busy").unwrap();
        }
    });
}

#[tauri::command]
fn download_file(window: tauri::Window, state: tauri::State<Data>, name: String, len: u64, path: String) {
    fn handle(conn: &mut Connection, name: &str, len: u64, path: &str) -> Result<(), String> {
        let offset = get_file_len(&path);
        if offset >= len {
            return Err("file size >= total size".to_owned());
        }

        conn.download_file(&name, offset, len - offset, &path)?;

        let expected_hash = conn.get_hash(&name)?;

        let hash = util::file_hash(&path)
            .map_err(|e| format!("Failed to compute hash: {e}"))?;

        if hash == expected_hash {
            Ok(())
        } else {
            Err("Incorrect file hash".to_owned())
        }
    }

    let conn = state.connection.clone();
    thread::spawn(move || {
        if let Ok(mut conn) = conn.try_lock() {
            if let Some(conn) = &mut *conn {
                if let Err(e) = handle(conn, &name, len, &path) {
                    window.emit("error", e).unwrap();
                } else {
                    window.emit("downloaded", &name).unwrap();
                }
            } else {
                window.emit("error", "Not connected").unwrap();
            }
        } else {
            window.emit("error", "System busy").unwrap();
        }
    });
}

#[tauri::command]
fn upload_file(window: tauri::Window, state: tauri::State<Data>, name: Option<String>, offset: Option<u64>, path: String) {
    let conn = state.connection.clone();
    thread::spawn(move || {
        if let Ok(mut conn) = conn.try_lock() {
            if let Some(conn) = &mut *conn {
                let name = if let None = name {
                    util::get_file_name(&path)
                } else {
                    name
                };
                if let Some(name) = name {
                    if let Err(e) = conn.upload_file(&name, offset, &path) {
                        window.emit("error", e).unwrap();
                    } else {
                        window.emit("uploaded", &name).unwrap();
                    }
                } else {
                    window.emit("error", "Cannot get file name from path").unwrap();
                }
            } else {
                window.emit("error", "Not connected").unwrap();
            }
        } else {
            window.emit("error", "System busy").unwrap();
        }
    });
}

fn main() {
    tauri::Builder::default()
        .manage(Data { connection: Arc::new(Mutex::new(None)) })
        .invoke_handler(tauri::generate_handler![
            connect,
            disconnect,
            fetch_list,
            download_file,
            upload_file
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
